/* 
* Copyright (c) Harry Pierson. 
*
* This source code is subject to terms and conditions of the Microsoft 
* Permissive License. A copy of the license can be found in the License.html 
* file at the root of this distribution. If you cannot locate the  Microsoft 
* Permissive License, please send an email to harry@devhawk.net. By using this 
* source code in any fashion, you are agreeing to be bound by the terms of the 
* Microsoft Permissive License.
* 
* You must not remove this notice, or any other, from this software.
*/

using System;
using System.Web;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Specialized;
using HawkWiki.WikiRenderer;

namespace HawkWiki.SampleWiki
{
	public class WikiHandler : IHttpHandler
	{
		IDictionary _pageExistCache = new HybridDictionary();

		#region IHttpHandler implementation
		public void ProcessRequest(HttpContext context)
		{
			//the pageExistCache is the only state stored in the handler class instances
			//since pages can be reused (IsReusable is hardcoded to always return true) 
			//it's important to clear this cache on every request. This cache is only used
			//when processing a version request (normal requests are compiled and all the 
			//page existance info is prechecked.)
			_pageExistCache.Clear();

			int qsIndex = context.Request.RawUrl.LastIndexOf('?');
			if (qsIndex == -1)
			{
				ProcessNormalRequest(context);
				return;
			}

			string qs = context.Request.RawUrl.Substring(qsIndex+1).ToLower();
			if (qs =="edit")
			{
				if (context.Request.HttpMethod == "GET")
				{
					ProcessEditGetRequest(context);
					return;
				}
				
				if (context.Request.HttpMethod == "POST")
				{
					ProcessEditPostRequest(context);
					return;
				}
			}
			
			if (qs == "versions")
			{
				//render versions page
				ProcessVersionsRequest(context);
				return;
			}

			if (qs.StartsWith("version="))
			{
				//render specific version page
				ProcessVersionRequest(context);
				return;
			}

			ProcessNormalRequest(context);
		
		}

		public bool IsReusable
		{
			get { return true; }
		}
		#endregion
	
		public void ProcessNormalRequest(HttpContext context)
		{
			string pageName = GetWikiPageName(context);

			if (!WikiDataProvider.Instance.CheckPageExists(pageName))
			{
				context.Response.Redirect(string.Format("{0}?edit", context.Request.Path), true);
				return;
			}
			
			//Retrieve the handler type (to check in case the object has been thrown
			//out and needs to be recompiled)
			Type handlerType = WikiDataProvider.Instance.GetCachedType(pageName);

			if (handlerType == null)
			{
				handlerType = GenerateCode(context);
				if (handlerType == null)
					throw new ApplicationException(string.Format("Error generating code for {0} page", pageName));
			}

			//Create a new instance of the handler type 
			WikiPage page = (WikiPage)Activator.CreateInstance(handlerType);
			page.WikiWordValidate = new WikiPage.WikiWordValidateDelegate(this.ValidatePages);

			//write chome
			//TODO: enable chrome extensibility
			System.IO.TextWriter tw = context.Response.Output;
			tw.WriteLine("<html><head><title>{0}</title></head>", pageName);
			tw.WriteLine("<body><h1>{0}</h1><hr/>", pageName);

			tw.WriteLine("\n<!-- START WIKI TEXT -->\n");
			page.ProcessWiki(new HtmlWikiWriter(tw));
			tw.WriteLine("\n<!-- END WIKI TEXT -->\n");

			string showCodePath = System.IO.Path.ChangeExtension(context.Request.Path, ".showcode");
			tw.WriteLine("<hr/><a href='{0}'>Show Generated Code</a><br/> ", showCodePath);
			tw.WriteLine("<a href='{0}?edit'>Edit This Page</a><br/>", context.Request.Path);
			tw.WriteLine("<a href='{0}?versions'>Show Versions of This Page</a><br/>", context.Request.Path);
			tw.WriteLine("<a href='StartPage.wiki'>Return to StartPage</a>");
			tw.WriteLine("</body></html>");
		}

		public void ProcessVersionsRequest(HttpContext context)
		{
			string pageName = GetWikiPageName(context);

			System.IO.TextWriter tw = context.Response.Output;
			tw.WriteLine("<html><head><title>{0} Versions</title></head>", pageName);
			tw.WriteLine("<body>");
			tw.WriteLine("<h1>{0} Versions</h1><hr/><ul>", pageName);
			tw.WriteLine("<li><a href='{0}'>Current Version</a></li>", RenderPageLink(pageName));

			WikiDataProvider.VersionInfo[] arVersions = WikiDataProvider.Instance.GetPageHistory(pageName);

			foreach (WikiDataProvider.VersionInfo version in arVersions)
			{
				tw.WriteLine("<li><a href='{0}?version={1}'>{2:MMMM d, yyyy} at {2:h:mm tt}</a></li>", 
					System.IO.Path.GetFileName(context.Request.Path), 
					version.VersionName, 
					version.VersionTimestamp);
			}

			tw.WriteLine("</ul>");
			tw.WriteLine("</body></head>");

			tw.Close();
		}

		public void ProcessVersionRequest(HttpContext context)
		{
			string pageName = GetWikiPageName(context);
			
			//get version name
			string versionName = context.Request.QueryString["version"];
			if (versionName == null || versionName.Length == 0)
				throw new ApplicationException("ProcessVersionRequest called without a version query string value");


			DateTime timestamp;
			System.IO.StreamReader sr = WikiDataProvider.Instance.GetPageVersionContent(pageName, versionName, out timestamp);

			//write chome
			//TODO: enable chrome extensibility
			System.IO.TextWriter tw = context.Response.Output;
			tw.WriteLine("<html><head><title>{0}</title></head>", pageName);
			tw.WriteLine("<body><h1>{0} ({1:MMMM d, yyyy} at {1:h:mm tt} Version)</h1><hr/>", pageName, timestamp);

			HtmlWikiWriter writer = new HtmlWikiWriter(tw);
			writer.WikiWordExists = new HtmlWikiWriter.WikiWordExistsDelegate(this.CheckPageExistsCached);
			writer.WikiWordLink = new HtmlWikiWriter.WikiWordLinkDelegate(this.RenderPageLink);

			tw.WriteLine("\n<!-- START WIKI TEXT -->\n");
            HawkWiki.WikiRenderer.WikiRenderer render = new HawkWiki.WikiRenderer.WikiRenderer(sr);
			render.ProcessWiki(writer);
			sr.Close();
			tw.WriteLine("\n<!-- END WIKI TEXT -->\n");

			tw.WriteLine("<hr/><a href='{0}'>Show Current version of This Page</a><br/>", context.Request.Path);
			tw.WriteLine("<a href='StartPage.wiki'>Return to StartPage</a>");
			tw.WriteLine("</body></html>");
		}

		public void ProcessEditGetRequest(HttpContext context)
		{
			string pageName = GetWikiPageName(context);
			
			System.IO.TextWriter tw = context.Response.Output;

			tw.WriteLine("<html><head><title>Edit {0}</title></head>", pageName);
			tw.WriteLine("<body><form method='POST'>");
			tw.WriteLine("<h1>Edit {0} <input type='submit' value='Save'> ", pageName);
			tw.WriteLine("<input type='button' value='Cancel' onClick='window.navigate(\"{0}\")'></h1>", 
				WikiDataProvider.Instance.CheckPageExists(pageName) 
					? RenderPageLink(pageName) 
					: RenderPageLink("StartPage"));
			tw.WriteLine("<p><blockquote><textarea name='text' rows=34 cols=96>");

			System.IO.TextReader tr = WikiDataProvider.Instance.GetPageContent(pageName);
			if (tr != null)
			{
				tw.WriteLine(tr.ReadToEnd());
				tr.Close();
			}

			tw.WriteLine("</textarea></blockquote><p>");
			tw.WriteLine("</form></body></head>");

			tw.Close();
		}

		public void ProcessEditPostRequest(HttpContext context)
		{
			string trimmedText = context.Request.Form["text"].TrimEnd();
			string pageName = GetWikiPageName(context);

			WikiDataProvider.Instance.UpdatePageContent(pageName, trimmedText);

			if (trimmedText.Length == 0)
				context.Response.Redirect(RenderPageLink("StartPage"), true);
			else
				context.Response.Redirect(RenderPageLink(pageName), true);
		}


		public Type GenerateCode(HttpContext context)
		{
			string pageName = GetWikiPageName(context);

			//Get the compiled version of the wiki page
			StringCollection refAssemblies = new StringCollection();
			CodeNamespace ns = WikiHandler.GenerateWikiCode(
				pageName,
				WikiHandler.GetWikiClassname(context),
				refAssemblies);

			if (ns == null)
				return null;

			//Create an instance of the C# compiler
			CodeDomProvider cdp = new Microsoft.CSharp.CSharpCodeProvider();
			ICodeCompiler icc = cdp.CreateCompiler();

			//Add each assembly specified by the CodeGenerator to the list of referenced assemblies
			CompilerParameters cp = new CompilerParameters();
			cp.IncludeDebugInformation = false;
#if DEBUG
			cp.IncludeDebugInformation = true;
#endif
			foreach(string buf in refAssemblies)
			{
				//Determine if the required assembly is in the /bin directory
				bool exist = System.IO.File.Exists(HttpRuntime.BinDirectory + buf);

				//If the assembly is in the /bin directory, add the assembly using the full path
				//else let the .NET runtime bind the assembly
				if (exist)
					cp.ReferencedAssemblies.Add(HttpRuntime.BinDirectory + buf);
				else
					cp.ReferencedAssemblies.Add(buf);
			}


			//Compile the generated code. Throw an exception if there are any compile errors
			CodeCompileUnit ccu = new CodeCompileUnit();
			ccu.Namespaces.Add(ns);
			CompilerResults cr = icc.CompileAssemblyFromDom(cp, ccu);
			if (cr.Errors.HasErrors)
			{
				string errBuf = "Compile Fail\n";
				foreach (CompilerError e in cr.Errors )
				{
					errBuf += "\t(L,C): (" + e.Line + "," + e.Column + ") " + e.ErrorNumber + ": " + e.ErrorText + "\n";
				}
				throw new ApplicationException(errBuf);
			}

			//Insert the newly compiled handler type into the cache. Use a cache dependency on the
			//XML file in order to keep the dynamic handler up to date.
			Type handlerType = cr.CompiledAssembly.GetType(GenCodeNamespace + "." + GetWikiClassname(context), true);
			WikiDataProvider.Instance.CacheType(pageName, handlerType);

			return handlerType;
		}


		#region WikiWord Exists, Links and Validation Delegates


		public bool CheckPageExistsCached(string pageName)
		{
			object o = _pageExistCache[pageName];
			if (o != null)
			{
				return (bool)o;
			}
			else
			{
				bool b = WikiDataProvider.Instance.CheckPageExists(pageName);
				_pageExistCache[pageName] = b;
				return b;
			}
		}


		public string RenderPageLink(string pageName)
		{
			return string.Format("{0}.wiki", pageName);
		}

		public IDictionary ValidatePages(StringCollection wikiPages)
		{
			IDictionary wikiStates = new HybridDictionary(wikiPages.Count);
			foreach (string pageName in wikiPages)
			{
				WikiPage.State s = new WikiPage.State();
				s.PageExists = WikiDataProvider.Instance.CheckPageExists(pageName);
				s.PageLink = RenderPageLink(pageName);
				wikiStates.Add(pageName, s);
			}
			return wikiStates;
		}


		#endregion

		#region static helper methods 
		public static string GetWikiClassname(HttpContext context)
		{
			return context.Request.Path.Replace('.', '_').Replace('/', '_').ToLower();
		}


		public static string GenCodeNamespace
		{
            get { return "HawkWiki.SampleWiki.Dynamic"; }
		}

		public static string GetWikiPageName(System.Web.HttpContext context)
		{
			return System.IO.Path.GetFileNameWithoutExtension(context.Request.Path);
		}

		public static CodeNamespace GenerateWikiCode(string pageName, string classname, StringCollection refAssemblies)
		{
			System.IO.StreamReader sr = WikiDataProvider.Instance.GetPageContent(pageName);
			if (sr == null)
				return null;

			WikiGenerator gen = new WikiGenerator(sr);
			CodeNamespace ns = gen.GenerateCode( 
				WikiHandler.GenCodeNamespace, 
				classname,
				refAssemblies);
			
			sr.Close();

			return ns;
		}

		#endregion
	}
}
